package search;

import helpers.DateHelper;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.logging.Logger;

import javax.cache.Cache;
import javax.cache.CacheException;
import javax.cache.CacheManager;

import search.result.ArticleSearchResult;

import com.google.appengine.api.NamespaceManager;
import com.google.appengine.api.search.Index;
import com.google.appengine.api.search.IndexSpec;
import com.google.appengine.api.search.Query;
import com.google.appengine.api.search.QueryOptions;
import com.google.appengine.api.search.Results;
import com.google.appengine.api.search.ScoredDocument;
import com.google.appengine.api.search.SearchServiceFactory;
import com.google.appengine.api.search.SortExpression;
import com.google.appengine.api.search.SortOptions;

import entities.ArticleSearch;

public class ArticleSearchModel {
	
	private Logger log = null;
	
	private String prefix = "article_search_";
	
	private Cache cache = null;
	
	private static ArticleSearchModel instance = null;
	
	public static ArticleSearchModel getInstance() {
		if (instance == null) {
			instance = new ArticleSearchModel();
		}
		
		return instance;
	}
	
	private ArticleSearchModel() {
		try {
            cache = CacheManager.getInstance().getCacheFactory().createCache(Collections.emptyMap());
        } catch (CacheException e) {
           //e.printStackTrace();
        }
        
        log = Logger.getLogger(ArticleSearchModel.class.getName());
	}
	
	public ArticleSearch getById(String ns, String id) {
		
		Index INDEX = SearchServiceFactory.getSearchService().getIndex(
				IndexSpec.newBuilder().setName(NamespaceManager.get()));
		
		String currentNs = NamespaceManager.get();
		if (!currentNs.equals(ns)) {
			NamespaceManager.set(ns);
		}
		
		try {
			ArticleSearch obj = new ArticleSearch();
			
			String cacheName = prefix + "id_" + id;
			boolean isCached = false;
			
			if (cache != null) {
				try {
					obj = (ArticleSearch) cache.get(cacheName);
					if (obj != null) isCached = true;
				} catch (Exception e) {
				}
			}
			
			if (!isCached) {
				QueryOptions options = QueryOptions.newBuilder().setLimit(1)
										.build();
				Query query = Query.newBuilder().setOptions(options)
								.build("id:\"" + id + "\"");
				Results<ScoredDocument> docResult = INDEX.search(query);
				if (docResult.getNumberFound() > 0) {
					for (ScoredDocument scoredDocument : docResult) {
						obj = ArticleSearchEngine.documentToObjectByReflection(scoredDocument);
					}
				}
				
				if (cache != null && obj != null && obj.getId().length() > 0) {
					cache.put(cacheName, obj);
				} else if (obj != null && obj.getId().length() == 0) {
					obj = null;
				}
			}
			
			NamespaceManager.set(currentNs);
			
			return obj;
		} catch (Exception e) {
			log.warning(e.toString());
			e.printStackTrace();
			
			NamespaceManager.set(currentNs);
			
			return null;
		}
	}
	
	public ArticleSearchResult list(String ns, int page, int limit) {
		Index INDEX = SearchServiceFactory.getSearchService().getIndex(
				IndexSpec.newBuilder().setName(NamespaceManager.get()));
		
		String currentNs = NamespaceManager.get();
		if (!currentNs.equals(ns)) {
			NamespaceManager.set(ns);
		}
		
		
		ArticleSearchResult result = new ArticleSearchResult();
		List<ArticleSearch> articles = new ArrayList<ArticleSearch>();
		
		try {
			int start = (page - 1) * limit;
			int numberFound = 0;
			int numberReturn = 0;
			
			StringBuilder queryString = new StringBuilder();
			//queryString.append("status:" + 2);
			Query query = Query
					.newBuilder()
					.setOptions(
							QueryOptions
									.newBuilder().setLimit(limit)
									.setOffset(start).build()
					)
					.build(queryString.toString());
			Results<ScoredDocument> docResult = INDEX.search(query);
			numberFound = (int) docResult.getNumberFound();
			numberReturn = (int) docResult.getNumberReturned();
			
			if (docResult.getNumberFound() > 0) {
				ArticleSearch obj = new ArticleSearch();
				for (ScoredDocument scoredDocument : docResult) {
					obj = ArticleSearchEngine
							.documentToObjectByReflection(scoredDocument);
					articles.add(obj);
				}
			}
			
			result.setLimit(limit);
			result.setPage(page);
			result.setNumberFound(numberFound);
			result.setNumberReturn(numberReturn);
			result.setArticles(articles);
			
		} catch (Exception e) {
			log.warning(e.toString());
			e.printStackTrace();
		}
		
		NamespaceManager.set(currentNs);
		
		return result;
	}
	
	public ArticleSearchResult listByTag1(String ns, String name, int page, int limit) {

		Index INDEX = SearchServiceFactory.getSearchService().getIndex(
				IndexSpec.newBuilder().setName(NamespaceManager.get()));
		
		String currentNs = NamespaceManager.get();
		if (!currentNs.equals(ns)) {
			NamespaceManager.set(ns);
		}
		
		
		ArticleSearchResult result = new ArticleSearchResult();
		List<ArticleSearch> articles = new ArrayList<ArticleSearch>();
		
		try {
			int start = (page - 1) * limit;
			int numberFound = 0;
			int numberReturn = 0;
			
			StringBuilder queryString = new StringBuilder();
			queryString.append("tags: " + name);
			queryString.append("status: 1");
			
			QueryOptions options = QueryOptions.newBuilder()
					.setNumberFoundAccuracy(1000).build();
			
			
			//Get total number found
			Query query = Query.newBuilder().setOptions(options)
							.build(queryString.toString());
			
			Results<ScoredDocument> docResult = INDEX.search(query);
			numberFound = (int) docResult.getNumberFound();
			
			//Get result
			SortOptions sortOptions = SortOptions
					.newBuilder()
					.addSortExpression(
							SortExpression
									.newBuilder()
									.setExpression("created")
									.setDirection(
											SortExpression.SortDirection.DESCENDING)
									.setDefaultValueNumeric(1)).build();
			
			options = QueryOptions
				.newBuilder()
				.setSortOptions(sortOptions)
				.setNumberFoundAccuracy(1000)
				.setLimit(limit)
				.setOffset(start)
				.build();
			
			query = Query.newBuilder().setOptions(options)
						.build(queryString.toString());
			
			docResult = INDEX.search(query);
			numberReturn = (int) docResult.getNumberReturned();
			if (docResult.getNumberFound() > 0) {
				ArticleSearch obj = new ArticleSearch();
				for (ScoredDocument scoredDocument : docResult) {
					obj = ArticleSearchEngine
							.documentToObjectByReflection(scoredDocument);
					articles.add(obj);
				}
			}
			
			result.setLimit(limit);
			result.setPage(page);
			result.setNumberFound(numberFound);
			result.setNumberReturn(numberReturn);
			result.setArticles(articles);
			
		} catch (Exception e) {
			log.warning(e.toString());
			e.printStackTrace();
		}
		
		NamespaceManager.set(currentNs);
		
		return result;
	}
	
	public ArticleSearchResult searchByType(String ns, String name, int page, int limit, String type) {
		Index INDEX = SearchServiceFactory.getSearchService().getIndex(
				IndexSpec.newBuilder().setName(NamespaceManager.get()));
		
		String currentNs = NamespaceManager.get();
		if (!currentNs.equals(ns)) {
			NamespaceManager.set(ns);
		}
		
		ArticleSearchResult result = new ArticleSearchResult();
		List<ArticleSearch> articles = new ArrayList<ArticleSearch>();
		
		try {
			int start = (page - 1) * limit;
			int numberFound = 0;
			int numberReturn = 0;
			
			StringBuilder queryString = new StringBuilder();
			queryString.append("status: 1");
			if (type.equalsIgnoreCase("tag")) {
				queryString.append("tags: " + name);
			}
			
			if (type.equalsIgnoreCase("category")) {
				queryString.append("category: " + name);
			}
			
			if (type.equalsIgnoreCase("author")) {
				queryString.append("creator: " + name);
			}
			
			if (type.equalsIgnoreCase("title")) {
				queryString.append("title: " + name);
			}
			
			if (type.equalsIgnoreCase("date")) {
				String formatInput = "dd-MM-yyyy";
				String formatOutput = "yyyy-MM-dd";
				String inDate = "01-" + name;
				
				String startDate = DateHelper.convertStringDate(inDate, formatInput, formatOutput);
				String endDate = DateHelper.getEndMonth(startDate, formatOutput);
				
				queryString.append("created >= " + startDate);
				queryString.append(" AND created <= " + endDate);
			}
			
			QueryOptions options = QueryOptions.newBuilder()
					.setNumberFoundAccuracy(1000).build();
			
			
			//Get total number found
			Query query = Query.newBuilder().setOptions(options)
							.build(queryString.toString());
			Results<ScoredDocument> docResult = INDEX.search(query);
			numberFound = (int) docResult.getNumberFound();
			
			//Get result
			SortOptions sortOptions = SortOptions
					.newBuilder()
					.addSortExpression(
							SortExpression
									.newBuilder()
									.setExpression("created")
									.setDirection(
											SortExpression.SortDirection.DESCENDING)
									.setDefaultValueNumeric(1)).build();
			
			options = QueryOptions
				.newBuilder()
				.setSortOptions(sortOptions)
				.setNumberFoundAccuracy(1000)
				.setLimit(limit)
				.setOffset(start)
				.build();
			
			query = Query.newBuilder().setOptions(options)
						.build(queryString.toString());
			
			docResult = INDEX.search(query);
			numberReturn = (int) docResult.getNumberReturned();
			if (docResult.getNumberFound() > 0) {
				ArticleSearch obj = new ArticleSearch();
				for (ScoredDocument scoredDocument : docResult) {
					obj = ArticleSearchEngine
							.documentToObjectByReflection(scoredDocument);
					articles.add(obj);
				}
			}
			
			result.setLimit(limit);
			result.setPage(page);
			result.setNumberFound(numberFound);
			result.setNumberReturn(numberReturn);
			result.setArticles(articles);
			
		} catch (Exception e) {
			log.warning(e.toString());
			e.printStackTrace();
		}
		
		NamespaceManager.set(currentNs);
		
		return result;
	}
	
	public ArticleSearchResult listArticleUnpublish(String ns, int page, int limit) {
		Index INDEX = SearchServiceFactory.getSearchService().getIndex(
				IndexSpec.newBuilder().setName(NamespaceManager.get()));
		
		String currentNs = NamespaceManager.get();
		if (!currentNs.equals(ns)) {
			NamespaceManager.set(ns);
		}
		
		ArticleSearchResult result = new ArticleSearchResult();
		List<ArticleSearch> articles = new ArrayList<ArticleSearch>();
		
		try {
			int start = (page - 1) * limit;
			int numberFound = 0;
			int numberReturn = 0;
			
			Calendar cal = Calendar.getInstance();
			cal.add(Calendar.DATE, -1);
			String formatOutput = "yyyy-MM-dd";
			
			String startDate = DateHelper.getStringFromDate(cal.getTime(), formatOutput);
			
			StringBuilder queryString = new StringBuilder();
			//queryString.append("created <= " + startDate);
			queryString.append("published:false");
			
			QueryOptions options = QueryOptions.newBuilder()
					.setNumberFoundAccuracy(1000).build();
			
			//Get total number found
			Query query = Query.newBuilder().setOptions(options)
							.build(queryString.toString());
			
			Results<ScoredDocument> docResult = INDEX.search(query);
			numberFound = (int) docResult.getNumberFound();
			
			//Get result
			SortOptions sortOptions = SortOptions
					.newBuilder()
					.addSortExpression(
							SortExpression
									.newBuilder()
									.setExpression("created")
									.setDirection(
											SortExpression.SortDirection.DESCENDING)
									.setDefaultValueNumeric(1)).build();
			
			options = QueryOptions
				.newBuilder()
				.setSortOptions(sortOptions)
				.setNumberFoundAccuracy(1000)
				.setLimit(limit)
				.setOffset(start)
				.build();
			
			System.out.println("-----Querry---");
			System.out.println(queryString.toString());
			query = Query.newBuilder().setOptions(options)
						.build(queryString.toString());
			
			docResult = INDEX.search(query);
			numberReturn = (int) docResult.getNumberReturned();
			System.out.println(numberReturn);
			if (docResult.getNumberFound() > 0) {
				ArticleSearch obj = new ArticleSearch();
				for (ScoredDocument scoredDocument : docResult) {
					obj = ArticleSearchEngine
							.documentToObjectByReflection(scoredDocument);
					articles.add(obj);
				}
			}
			
			result.setLimit(limit);
			result.setPage(page);
			result.setNumberFound(numberFound);
			result.setNumberReturn(numberReturn);
			result.setArticles(articles);
			
		} catch (Exception e) {
			log.warning(e.toString());
			e.printStackTrace();
		}
		
		NamespaceManager.set(currentNs);
		
		return result;
	}
}
